{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "64a9da69",
   "metadata": {},
   "source": [
    "## SETUP"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1e89c815",
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install gym[classic_control]\n",
    "!pip install dezero"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d354811c",
   "metadata": {},
   "source": [
    "## ch08/gym_play.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "923b86c1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "action: 0\n",
      "action: 0\n",
      "action: 0\n",
      "action: 1\n",
      "action: 1\n",
      "action: 0\n",
      "action: 0\n",
      "action: 0\n",
      "action: 0\n",
      "action: 0\n",
      "action: 0\n",
      "action: 1\n",
      "action: 0\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "import gym\n",
    "\n",
    "\n",
    "env = gym.make('CartPole-v0')\n",
    "state = env.reset()\n",
    "done = False\n",
    "\n",
    "while not done:\n",
    "    #env.render()\n",
    "    action = np.random.choice([0, 1])\n",
    "    next_state, reward, done, info = env.step(action)\n",
    "    print('action:', action)\n",
    "#env.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3c0b72d",
   "metadata": {},
   "source": [
    "## ch08/replay_buffer.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "41011871",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(32, 4)\n",
      "(32,)\n",
      "(32,)\n",
      "(32, 4)\n",
      "(32,)\n"
     ]
    }
   ],
   "source": [
    "from collections import deque\n",
    "import random\n",
    "import numpy as np\n",
    "import gym\n",
    "\n",
    "\n",
    "class ReplayBuffer:\n",
    "    def __init__(self, buffer_size, batch_size):\n",
    "        self.buffer = deque(maxlen=buffer_size)\n",
    "        self.batch_size = batch_size\n",
    "\n",
    "    def add(self, state, action, reward, next_state, done):\n",
    "        data = (state, action, reward, next_state, done)\n",
    "        self.buffer.append(data)\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.buffer)\n",
    "\n",
    "    def get_batch(self):\n",
    "        data = random.sample(self.buffer, self.batch_size)\n",
    "\n",
    "        state = np.stack([x[0] for x in data])\n",
    "        action = np.array([x[1] for x in data])\n",
    "        reward = np.array([x[2] for x in data])\n",
    "        next_state = np.stack([x[3] for x in data])\n",
    "        done = np.array([x[4] for x in data]).astype(np.int32)\n",
    "        return state, action, reward, next_state, done\n",
    "\n",
    "\n",
    "env = gym.make('CartPole-v0')\n",
    "replay_buffer = ReplayBuffer(buffer_size=10000, batch_size=32)\n",
    "\n",
    "for episode in range(10):\n",
    "    state = env.reset()\n",
    "    done = False\n",
    "\n",
    "    while not done:\n",
    "        action = 0\n",
    "        next_state, reward, done, info = env.step(action)\n",
    "        replay_buffer.add(state, action, reward, next_state, done)\n",
    "        state = next_state\n",
    "\n",
    "state, action, reward, next_state, done = replay_buffer.get_batch()\n",
    "print(state.shape)  # (32, 4)\n",
    "print(action.shape)  # (32,)\n",
    "print(reward.shape)  # (32,)\n",
    "print(next_state.shape)  # (32, 4)\n",
    "print(done.shape)  # (32,)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13c09484",
   "metadata": {},
   "source": [
    "## ch08/dqn.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5e448679",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "episode :0, total reward : 14.0\n",
      "episode :10, total reward : 98.0\n",
      "episode :20, total reward : 38.0\n",
      "episode :30, total reward : 20.0\n",
      "episode :40, total reward : 12.0\n",
      "episode :50, total reward : 10.0\n",
      "episode :60, total reward : 23.0\n",
      "episode :70, total reward : 17.0\n",
      "episode :80, total reward : 9.0\n",
      "episode :90, total reward : 124.0\n",
      "episode :100, total reward : 122.0\n",
      "episode :110, total reward : 186.0\n",
      "episode :120, total reward : 156.0\n",
      "episode :130, total reward : 198.0\n",
      "episode :140, total reward : 146.0\n",
      "episode :150, total reward : 200.0\n",
      "episode :160, total reward : 200.0\n",
      "episode :170, total reward : 193.0\n",
      "episode :180, total reward : 200.0\n",
      "episode :190, total reward : 142.0\n",
      "episode :200, total reward : 200.0\n",
      "episode :210, total reward : 200.0\n",
      "episode :220, total reward : 179.0\n",
      "episode :230, total reward : 149.0\n",
      "episode :240, total reward : 200.0\n",
      "episode :250, total reward : 200.0\n",
      "episode :260, total reward : 200.0\n",
      "episode :270, total reward : 200.0\n",
      "episode :280, total reward : 200.0\n",
      "episode :290, total reward : 200.0\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total Reward: 200.0\n"
     ]
    }
   ],
   "source": [
    "import copy\n",
    "from collections import deque\n",
    "import random\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import gym\n",
    "from dezero import Model\n",
    "from dezero import optimizers\n",
    "import dezero.functions as F\n",
    "import dezero.layers as L\n",
    "\n",
    "\n",
    "class ReplayBuffer:\n",
    "    def __init__(self, buffer_size, batch_size):\n",
    "        self.buffer = deque(maxlen=buffer_size)\n",
    "        self.batch_size = batch_size\n",
    "\n",
    "    def add(self, state, action, reward, next_state, done):\n",
    "        data = (state, action, reward, next_state, done)\n",
    "        self.buffer.append(data)\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.buffer)\n",
    "\n",
    "    def get_batch(self):\n",
    "        data = random.sample(self.buffer, self.batch_size)\n",
    "\n",
    "        state = np.stack([x[0] for x in data])\n",
    "        action = np.array([x[1] for x in data])\n",
    "        reward = np.array([x[2] for x in data])\n",
    "        next_state = np.stack([x[3] for x in data])\n",
    "        done = np.array([x[4] for x in data]).astype(np.int32)\n",
    "        return state, action, reward, next_state, done\n",
    "\n",
    "\n",
    "class QNet(Model):\n",
    "    def __init__(self, action_size):\n",
    "        super().__init__()\n",
    "        self.l1 = L.Linear(128)\n",
    "        self.l2 = L.Linear(128)\n",
    "        self.l3 = L.Linear(action_size)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.l1(x))\n",
    "        x = F.relu(self.l2(x))\n",
    "        x = self.l3(x)\n",
    "        return x\n",
    "\n",
    "\n",
    "class DQNAgent:\n",
    "    def __init__(self):\n",
    "        self.gamma = 0.98\n",
    "        self.lr = 0.0005\n",
    "        self.epsilon = 0.1\n",
    "        self.buffer_size = 10000\n",
    "        self.batch_size = 32\n",
    "        self.action_size = 2\n",
    "\n",
    "        self.replay_buffer = ReplayBuffer(self.buffer_size, self.batch_size)\n",
    "        self.qnet = QNet(self.action_size)\n",
    "        self.qnet_target = QNet(self.action_size)\n",
    "        self.optimizer = optimizers.Adam(self.lr)\n",
    "        self.optimizer.setup(self.qnet)\n",
    "\n",
    "    def get_action(self, state):\n",
    "        if np.random.rand() < self.epsilon:\n",
    "            return np.random.choice(self.action_size)\n",
    "        else:\n",
    "            state = state[np.newaxis, :]\n",
    "            qs = self.qnet(state)\n",
    "            return qs.data.argmax()\n",
    "\n",
    "    def update(self, state, action, reward, next_state, done):\n",
    "        self.replay_buffer.add(state, action, reward, next_state, done)\n",
    "        if len(self.replay_buffer) < self.batch_size:\n",
    "            return\n",
    "\n",
    "        state, action, reward, next_state, done = self.replay_buffer.get_batch()\n",
    "        qs = self.qnet(state)\n",
    "        q = qs[np.arange(self.batch_size), action]\n",
    "\n",
    "        next_qs = self.qnet_target(next_state)\n",
    "        next_q = next_qs.max(axis=1)\n",
    "        next_q.unchain()\n",
    "        target = reward + (1 - done) * self.gamma * next_q\n",
    "\n",
    "        loss = F.mean_squared_error(q, target)\n",
    "\n",
    "        self.qnet.cleargrads()\n",
    "        loss.backward()\n",
    "        self.optimizer.update()\n",
    "\n",
    "    def sync_qnet(self):\n",
    "        self.qnet_target = copy.deepcopy(self.qnet)\n",
    "\n",
    "episodes = 300\n",
    "sync_interval = 20\n",
    "env = gym.make('CartPole-v0')\n",
    "agent = DQNAgent()\n",
    "reward_history = []\n",
    "\n",
    "for episode in range(episodes):\n",
    "    state = env.reset()\n",
    "    done = False\n",
    "    total_reward = 0\n",
    "\n",
    "    while not done:\n",
    "        action = agent.get_action(state)\n",
    "        next_state, reward, done, info = env.step(action)\n",
    "\n",
    "        agent.update(state, action, reward, next_state, done)\n",
    "        state = next_state\n",
    "        total_reward += reward\n",
    "\n",
    "    if episode % sync_interval == 0:\n",
    "        agent.sync_qnet()\n",
    "\n",
    "    reward_history.append(total_reward)\n",
    "    if episode % 10 == 0:\n",
    "        print(\"episode :{}, total reward : {}\".format(episode, total_reward))\n",
    "\n",
    "\n",
    "# === Plot ===\n",
    "plt.xlabel('Episode')\n",
    "plt.ylabel('Total Reward')\n",
    "plt.plot(range(len(reward_history)), reward_history)\n",
    "plt.show()\n",
    "\n",
    "\n",
    "# === Play CartPole ===\n",
    "agent.epsilon = 0  # greedy policy\n",
    "state = env.reset()\n",
    "done = False\n",
    "total_reward = 0\n",
    "\n",
    "while not done:\n",
    "    action = agent.get_action(state)\n",
    "    next_state, reward, done, info = env.step(action)\n",
    "    state = next_state\n",
    "    total_reward += reward\n",
    "    #env.render()\n",
    "print('Total Reward:', total_reward)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
